//
// File:        rm_filehandle.cc
// Description: RM_FileHandle class implementation
// Authors:     Haris Wang (dynmiw@gmail.com)
//
// 
#include <cstdio>
#include <cstring>
#include <cmath>
#include <unistd.h>
#include <iostream>
#include "rm.h"
#include "rm_internal.h"

RM_FileHandle::~RM_FileHandle()
{
    if( pfHandle!=NULL )
    {
        delete pfHandle;
    }
}


RC RM_FileHandle::Open(PF_FileHandle* pfh, int size)
{
    if(bFileOpen || pfHandle != NULL)
        return (START_RM_ERR - 4);
    if(pfh==NULL || size<=0)
        return (START_RM_ERR - 5);
    
    bFileOpen = true;
    pfHandle = new PF_FileHandle;
    *pfHandle = *pfh;

    PF_PageHandle ph;
    pfHandle->GetThisPage(0, ph);
    pfHandle->UnpinPage(0);

    this->GetFileHeader(ph);

    bHdrChanged = 1;   
}


// persist header into the first page of a file for later
RC RM_FileHandle::SetFileHeader(PF_PageHandle ph) const
{
    char *buf;
    RC rc = ph.GetData(buf);
    memcpy(buf, &hdr, sizeof(hdr));
    return rc;
}


// get header from the first page of a newly opened file
RC RM_FileHandle::GetFileHeader(PF_PageHandle ph)
{
    char *buf;
    RC rc = ph.GetData(buf);
    memcpy(&hdr, buf, sizeof(hdr));
    return rc;
}


bool RM_FileHandle::hdrChanged() const
{
    return hdrChanged;
}


PageNum RM_FileHandle::GetNumPages() const
{
    return hdr.numPages;
}


SlotNum RM_FileHandle::GetNumSlots() const
{
    if(hdr.extRecordSize==0)
        return (START_RM_ERR - 3);
    
    int bytes_available = PF_PAGE_SIZE - sizeof(RM_PageHdr);
    int slots = floor(1.0 * bytes_available/ (hdr.extRecordSize+1/8));
    int r = sizeof(RM_PageHdr) + bitmap(slots).NumChars;

    while( slots*hdr.extRecordSize > bytes_available-bitmap(slots).NumChars )
    {
        slots--;
    }
    return slots;
}


//
// GetSlotPointer
//
// Desc: Get slot pointer
// Out:  pData
// Ret:  RM return code
//
RC RM_FileHandle::GetSlotPointer(PF_PageHandle ph, SlotNum s, char*& pData ) const
{
    RC invalid = IsValid();
    if(invalid)
        return invalid;
    
    RC rc = ph.GetData(pData);
    if( rc<0 )
        return rc;
    
    bitmap b(this->GetNumSlots());
    pData = pData + (RM_PageHdr(this->GetNumSlots()).size());
    pData = pData + s * hdr.extRecordSize;
    return rc;
}


RC RM_FileHandle::IsValid() const
{
    if((pfHandle==NULL) || !bFileOpen)
        return (START_RM_ERR - 6);
    if(GetNumSlots()<=0)
        return (START_RM_ERR - 3);
    
    return 0;
}


RC RM_FileHandle::SetHdr(RM_FileHdr h)
{
    hdr=h;
    return 0;
}

//
// GetRec
//
// Desc: Get record
// Out:  rec
// Ret:  RM return code
//
RC RM_FileHandle::GetRec(const RID &rid, RM_Record &rec) const
{
    RC invalid = IsValid();
    if(invalid)
        return invalid;
    if(!this->IsValidRID(rid))
        return (START_RM_ERR - 7);
    
    PageNum p; SlotNum s;
    rid.GetPageNum(p);
    rid.GetSlotNum(s);
    RC rc=0;
    PF_PageHandle ph;
    RM_PageHdr pHdr(this->GetNumSlots());
    if((rc=pfHandle->GetThisPage(p,ph)) || (rc=pfHandle->UnpinPage(p)) )
        return rc;

    char* buf;
    rc = ph.GetData(buf);
    pHdr.from_buf(buf);
    if(rc)
        return rc;

    bitmap b(this->GetNumSlots(), pHdr.freeSlotMap);
    if(b.test(s))
        return (START_RM_WARN + 1);
    
    char *pData = NULL;
    if(RC rc=this->GetSlotPointer(ph,s,pData))
        return rc;
    
    rec.Set(pData, hdr.extRecordSize, rid);
    return 0;
}

//
// DeleteRec
//
// Desc: Delete record
// Ret:  RM return code
//
RC RM_FileHandle::DeleteRec(const RID &rid)
{
    RC invalid = IsValid();
    if(invalid)
        return invalid;
    if(!this->IsValidRID(rid))
        return (START_RM_ERR - 7);
    
    PageNum p; SlotNum s;
    rid.GetPageNum(p);
    rid.GetSlotNum(s);
    RC rc = 0;
    PF_PageHandle ph;
    RM_PageHdr pHdr(this->GetNumSlots());
    if((rc = pfHandle->GetThisPage(p, ph)) ||
        (rc = pfHandle->MarkDirty(p)) ||
        (rc = pfHandle->UnpinPage(p)) )
        return rc;
        
    char* buf;
    rc = ph.GetData(buf);
    pHdr.from_buf(buf);
    if(rc)
        return rc;

    bitmap b(this->GetNumSlots(), pHdr.freeSlotMap);
    if(b.test(s))
        return (START_RM_WARN + 1);
    
    b.set(s,0);
    if(pHdr.numFreeSlots==0)
    {
        pHdr.nextFree = hdr.firstFree;
        hdr.firstFree = p;
    }
    pHdr.numFreeSlots++;

    b.to_char_buf(pHdr.freeSlotMap, b.NumChars);
    rc = ph.GetData(buf);
    if( rc ) return rc;
    pHdr.to_buf(buf);

    return 0;
}

//
// InsertRec
//
// Desc: Insert record
// Ret:  RM return code
//
RC RM_FileHandle::InsertRec(const char *pData, RID &rid)
{
    RC invalid = IsValid();
    if(invalid)
        return invalid;
    if(pData==NULL)
        return (START_RM_ERR - 2);

    PF_PageHandle ph;
    RM_PageHdr pHdr(this->GetNumSlots());
    PageNum p; SlotNum s;
    RC rc;
    char *pSlot;
    if(( rc = this->GetNextFreeSlot(ph, p, s) ))
        return rc;

    char* buf;
    rc = ph.GetData(buf);
    pHdr.from_buf(buf);
    if(rc)
        return rc;

    bitmap b(this->GetNumSlots(), pHdr.freeSlotMap);
    if(( rc = this->GetSlotPointer(ph, s, pSlot) ))
        return rc;
    rid = RID(p,s);
    memcpy(pSlot, pData, hdr.extRecordSize);

    b.set(s, 1);
    pHdr.numFreeSlots--;
    if(pHdr.numFreeSlots==0)
    {
        hdr.firstFree = pHdr.nextFree;
        pHdr.nextFree = RM_PAGE_FULLY_USED;
    }

    b.to_char_buf(pHdr.freeSlotMap, b.NumChars);

    rc = ph.GetData(buf);
    if( rc ) return rc;
    pHdr.to_buf(buf);

    return 0;
}

//
// UpdateRec
//
// Desc: Update record
// Ret:  RM return code
//
RC RM_FileHandle::UpdateRec(const RM_Record &rec)
{
    RC invalid = IsValid();
    if(invalid)
        return invalid;

    RID rid;
    rec.GetRid(rid);
    PageNum p; SlotNum s;
    rid.GetPageNum(p);
    rid.GetSlotNum(s);
    if(!this->IsValidRID(rid))
        return (START_RM_ERR - 7);

    PF_PageHandle ph;
    char *pSlot;
    RC rc;
    RM_PageHdr pHdr(this->GetNumSlots());
    if((rc = pfHandle->GetThisPage(p, ph)) ||
        (rc = pfHandle->MarkDirty(p)) ||
        (rc = pfHandle->UnpinPage(p)) )
        return rc;

    char* buf;
    rc = ph.GetData(buf);
    pHdr.from_buf(buf);
    if(rc)
        return rc;

    bitmap b(this->GetNumSlots(), pHdr.freeSlotMap);
    if(b.test(s))
        return (START_RM_WARN + 1);
    
    char *pData = NULL;
    rec.GetData(pData);

    if(RC rc=this->GetSlotPointer(ph, s, pSlot))
        return rc;

    memcpy(pSlot, pData, hdr.extRecordSize);
    return 0;
}


// Force a page from the buffer pool to disk. Default value forces all pages.
RC RM_FileHandle::ForcePages(PageNum pageNum)
{
    RC invalid = IsValid();
    if(invalid)
        return invalid;

    if(pageNum==ALL_PAGES | (bFileOpen && pageNum >= 0 && pageNum < hdr.numPages))
        return pfHandle->ForcePages(pageNum);

    return (START_RM_ERR - 7);
}


// 
// Is it a valid RID
//
bool RM_FileHandle::IsValidRID(const RID rid) const
{
    PageNum p; SlotNum s;

    rid.GetPageNum(p);
    if(!(p>=0 && p<hdr.numPages && bFileOpen))
    {
        return 0;
    }

    rid.GetSlotNum(s);
    if(!(s>=0 && s<this->GetNumSlots()))
    {
        return 0;
    }

    return 1;
}